using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcContrib.UI.Grid;

namespace etcetera.Mvc.UI.Grid
{
    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <remarks>
    /// The render sequence in the mvccontrib GridRenderer is defined as follows:
    /// RenderGridStart(); -> 
    /// RenderHeader();
    /// RenderItems(); or RenderEmpty();
    /// RenderGridEnd();
    /// </remarks>
    public class HtmlGridRenderer<T> : GridRenderer<T>, ISortableGridRenderer<T> where T : class
    {
        private ViewContext _context;
        private const string DefaultCssClass = "tblGrid";
        private string _sortQueryStringName = "sort";
        private string _sortOrderQueryStringName = "order";

        private string _currentOrderBy = string.Empty;
        private SortOrder _currentSortOrder = SortOrder.Ascending;

        /// <summary>
        /// Initializes a new instance of the <see cref="HtmlGridRenderer&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="engines">The engines.</param>
		public HtmlGridRenderer(ViewEngineCollection engines) : base(engines)
		{
		}

        /// <summary>
        /// Initializes a new instance of the <see cref="HtmlGridRenderer&lt;T&gt;"/> class.
        /// </summary>
        public HtmlGridRenderer() { }

        /// <summary>
        /// The name of the query string parameter which carries order direction. "order" by default.
        /// </summary>
        public string SortOrderQueryStringName
        {
            get { return _sortOrderQueryStringName; }
            set { _sortOrderQueryStringName = value; }
        }

        /// <summary>
        /// The name of the query string parameter which carries the name of the column to sort by. "sort" by default.
        /// </summary>
        public string SortQueryStringName
        {
            get { return _sortQueryStringName; }
            set { _sortQueryStringName = value; }
        }

        public string GridId { get; set; }
        public string PagerId { get; set; }

        public IEnumerable<string> ColWidths { get; set; }

        /// <summary>
        /// Renders the specified grid model.
        /// </summary>
        /// <param name="gridModel">The grid model.</param>
        /// <param name="dataSource">The data source.</param>
        /// <param name="output">The output.</param>
        /// <param name="viewContext">The view context.</param>
        public void Render(IGridModel<T> gridModel, ISortableDataSource<T> dataSource, TextWriter output, ViewContext viewContext)
        {
            _context = viewContext;
            base.Render(gridModel, dataSource, output, viewContext);
        }

        /// <summary>
        /// Renders the specified grid model.
        /// </summary>
        /// <param name="gridModel">The grid model.</param>
        /// <param name="dataSource">The data source.</param>
        /// <param name="output">The output.</param>
        /// <param name="context">The context.</param>
        public new void Render(IGridModel<T> gridModel, IEnumerable<T> dataSource, TextWriter output, ViewContext context)
        {
            _context = context;
            base.Render(gridModel, dataSource, output, context);
        }

        protected override void RenderGridStart()
        {
            if (!GridModel.Attributes.ContainsKey("class"))
                GridModel.Attributes["class"] = DefaultCssClass;

            string attrs = BuildHtmlAttributes(GridModel.Attributes);

            if (attrs.Length > 0)
                attrs = " " + attrs;

            RenderText(string.Format("<table{0}>", attrs));

            if (ColWidths != null && ColWidths.Count() > 0)
            {
                RenderText("<colgroup>");
                foreach (string width in ColWidths)
                {
                    RenderText(string.Format("<col width=\"{0}\" />", width));
                }
                RenderText("</colgroup>");
            }
        }

        protected override void RenderGridEnd(bool isEmpty)
        {
            RenderText("</table>");

            // Add the javascript to set the sorting params for Ajax calls.
            if (!string.IsNullOrEmpty(PagerId))
            {
                string script = "<script type='text/javascript'>$(document).ready(function() {";

                string format = "$('#{0}').pager({{ sort:'{1}',order:'{2}',gridId:'{3}'}});";

                script += string.Format(format, PagerId, _currentOrderBy, _currentSortOrder, GridId) + "});</script>";
                RenderText(script);
            }
        }

        protected override void RenderHeadStart()
        {
            string attributes = BuildHtmlAttributes(GridModel.Sections.HeaderRow.Attributes(new GridRowViewData<T>(null, false)));
            if(attributes.Length > 0)
                attributes = " " + attributes;

            //RenderText(string.Format("<thead><tr{0}>", attributes));
            RenderText(string.Format("<thead><tr{0}>", attributes));
        }

        protected override void RenderHeadEnd()
        {
            RenderText("</tr></thead>");
        }

        protected void RenderHeaderCellStart()
        {
            //RenderText("<th>"); // for some reason, <th> doesn't format well in IE when a th:hover backgroundcolor is applied.
            RenderText("<td>");
        }
		
        protected override void RenderHeaderCellStart(GridColumn<T> column)
		{
			string attrs = BuildHtmlAttributes(column.HeaderAttributes);
			if (attrs.Length > 0)
				attrs = " " + attrs;

			//RenderText(string.Format("<th{0}>", attrs));
			RenderText(string.Format("<td{0}>", attrs));
		}

        protected override void RenderHeaderText(GridColumn<T> column)
        {
            if (column.IsSortable)
            {
                EnsureSorting(column);
                string path = _context.RequestContext.HttpContext.Request.Path;
                string parameter = GetSortingParameter(column);
                string sortClass = GetColumnHeaderClass(column);
                RenderText(string.Format("<a href=\"{0}{1}\"{2}>", path, parameter, sortClass));
                base.RenderHeaderText(column);
                RenderText("</a>");
            }
            else
                base.RenderHeaderText(column);
        }

        private void EnsureSorting(GridColumn<T> column)
        {
            if (column.IsSortable) // && DataSource is ISortableDataSource<T>)
            {
                column.SortOptions.SortByQueryParameterName = _sortQueryStringName;
                column.SortOptions.SortOrderQueryParameterName = _sortOrderQueryStringName;
            }
        }

        private string GetSortingParameter(GridColumn<T> column)
        {
            string qsFormat = "?{0}={1}&{2}={3}{4}";
            string additionalParams = AdditionalParametersQuery(_context.RequestContext.HttpContext.Request, column);

            return String.Format(qsFormat, 
                                 column.SortOptions.SortByQueryParameterName, 
                                 column.Name, 
                                 column.SortOptions.SortOrderQueryParameterName, 
                                 GetColumnSortOrder(column), 
                                 additionalParams);
        }

        private string AdditionalParametersQuery(HttpRequestBase request, GridColumn<T> column)
        {
            string additionalParams = string.Empty;
            
            // Add any keys which are not sort name or sort order keys
            foreach (string key in request.QueryString.Keys)
            {
                if (key.ToLower().Equals(column.SortOptions.SortOrderQueryParameterName, StringComparison.InvariantCultureIgnoreCase) == false &&
                    key.ToLower().Equals(column.SortOptions.SortByQueryParameterName, StringComparison.InvariantCultureIgnoreCase) == false)
                {
                    additionalParams += string.Format("&{0}={1}", key, request.QueryString[key]);
                }
            }
            return additionalParams;
        }

        private SortOrder GetColumnSortOrder(GridColumn<T> column)
        {
            SortOrder sortOrder = SortOrder.Ascending;

            if (IsSortedByColumn(column) && GetNextSortOrder(column) == SortOrder.Descending)
                sortOrder = SortOrder.Descending;

            return sortOrder;
        }

        private string GetColumnHeaderClass(GridColumn<T> column)
        {
            string classFormat = string.Empty;
            if (IsSortedByColumn(column))
            {
                _currentOrderBy = column.Name;

                if (GetNextSortOrder(column) == SortOrder.Descending)
                {
                    classFormat = " class=\"hdrAsc\"";
                    _currentSortOrder = SortOrder.Ascending;
                }
                else
                {
                    classFormat = " class=\"hdrDesc\"";
                    _currentSortOrder = SortOrder.Descending;
                }
            }

            return classFormat;
        }

        private bool IsSortedByColumn(GridColumn<T> column)
        {
            bool ret = false;
            string sortByName = GetQueryStringValue(column.SortOptions.SortByQueryParameterName);
            
            if (!string.IsNullOrEmpty(sortByName) && sortByName.Equals(column.Name, StringComparison.InvariantCultureIgnoreCase) ||
               (string.IsNullOrEmpty(sortByName) && column.SortOptions.IsDefault))
                ret = true;

            return ret;
        }

        private SortOrder GetNextSortOrder(GridColumn<T> column)
        {
            SortOrder sortOrder = SortOrder.Ascending;
            string sortOrderValue = GetQueryStringValue(column.SortOptions.SortOrderQueryParameterName);

            if (!string.IsNullOrEmpty(sortOrderValue) && sortOrderValue.Equals("ascending", StringComparison.InvariantCultureIgnoreCase) ||
               (string.IsNullOrEmpty(sortOrderValue) && column.SortOptions.IsDefault))
                sortOrder = SortOrder.Descending;

            return sortOrder;
        }

        private string GetQueryStringValue(string key)
        {
            string value = null;
            HttpRequestBase request = _context.RequestContext.HttpContext.Request;

            foreach (string k in request.QueryString.Keys)
            {
                if (k.ToLower().Equals(key, StringComparison.InvariantCultureIgnoreCase))
                {
                    value = request.QueryString[k];
                    break;
                }
            }

            return value;
        }
        
        protected override void RenderHeaderCellEnd()
        {
            //RenderText("</th>");
            RenderText("</td>");
        }

        protected override void RenderBodyStart() 
        {
            RenderText("<tbody>");
        }

        protected override void RenderBodyEnd() 
        {
            RenderText("</tbody>");
        }

        protected override void RenderRowStart(GridRowViewData<T> rowData)
		{
			var attributes = GridModel.Sections.Row.Attributes(rowData);

			if (! attributes.ContainsKey("class"))
			{
                attributes["class"] = rowData.IsAlternate ? "alt" : string.Empty;
			}

			string attributeString = BuildHtmlAttributes(attributes);

			if(attributeString.Length > 0)
			{
				attributeString = " " + attributeString;	
			}

			RenderText(string.Format("<tr{0}>", attributeString));
		}

		protected override void RenderRowEnd()
		{
			RenderText("</tr>");
		}

        protected override void RenderStartCell(GridColumn<T> column, GridRowViewData<T> rowData)
		{
			string attrs = BuildHtmlAttributes(column.Attributes(rowData));
			if (attrs.Length > 0)
				attrs = " " + attrs;

			RenderText(string.Format("<td{0}>", attrs));
		}

        protected override void RenderEndCell()
        {
            RenderText("</td>");
        }
        protected override void RenderEmpty()
		{
		    RenderBodyStart();
			RenderText("<tr><td>" + GridModel.EmptyText + "</td></tr>");
            RenderBodyEnd();
		}

        /// <summary>
		/// Converts the specified attributes dictionary of key-value pairs into a string of HTML attributes. 
		/// </summary>
		/// <returns></returns>
		private static string BuildHtmlAttributes(IDictionary<string, object> attributes)
		{
			if (attributes == null || attributes.Count == 0)
			{
				return string.Empty;
			}

			const string attributeFormat = "{0}=\"{1}\"";

			return string.Join(" ", attributes.Select(pair => string.Format(attributeFormat, pair.Key, pair.Value)).ToArray()
			);
		}
    }
}